package com.magicalcoder.pigeon.client;

import com.alibaba.fastjson.JSON;
import com.magicalcoder.pigeon.common.dto.RmtAddress;
import com.magicalcoder.pigeon.zookeeper.ClientFoundService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by hzhedongyu on 2017/5/26.
 * 链路管理
 * 对持有的netty连接 handler进行灵活管理
 */
public class ClientHandlerManager {

    private Logger logger = LoggerFactory.getLogger(ClientHandlerManager.class);
    
    private AtomicInteger rerote = new AtomicInteger(0);

    private GCDelayHandler gcDelayHandler = new GCDelayHandler();
    
    private ReentrantLock lock = new ReentrantLock();
    private ReentrantLock lockAddHandler = new ReentrantLock();
    private Condition condition = lock.newCondition();
    
    private CopyOnWriteArrayList<NettyClientHandler> handlers = new CopyOnWriteArrayList<>();

    private static ClientHandlerManager instance = new ClientHandlerManager();

    private ClientHandlerManager(){
        
    }
    public static ClientHandlerManager get(){
        return instance;
    }


    public void removeHandler(String randomId){
        try {
            lockAddHandler.lock();
            if(handlers.size()>0){
                for(int i=0;i<handlers.size();i++){
                    NettyClientHandler oldHandler = handlers.get(i);
                    if(randomId.equals(oldHandler.getRandomId())){
                        oldHandler.close();
                        handlers.remove(i);
                        logger.error(key(oldHandler.getRmtAddress())+"客户端失去netty连接，清除handler");
                    }
                }
            }
            testHandlers();
        }finally {
            lockAddHandler.unlock();
        }
    }
    
    public void addHandler(NettyClientHandler newHandler){
        try {
            lockAddHandler.lock();
            boolean exist = false;
            if(handlers.size()>0){
                for(int i=0;i<handlers.size();i++){
                    NettyClientHandler oldHandler = handlers.get(i);
                    RmtAddress old = oldHandler.getRmtAddress();
                    String key = key(old);

                    RmtAddress add = newHandler.getRmtAddress();

                    String newKey = key(add);
                    if(key.equals(newKey)){
                        if(old.isDead()){
                            logger.error(key+"节点已经不存在,60秒后准备回收");
                            gcDelayHandler.delayGc(old,60,TimeUnit.SECONDS);
                        }else {
                            exist = true;
                        }
                    }
                }
            }
            //避免多余的handler
            if(!exist){
                handlers.add(newHandler);
                logger.info(key(newHandler.getRmtAddress())+"添加新节点成功");
            }else {
                newHandler.close();
                logger.info(key(newHandler.getRmtAddress())+"已经存在对应连接，执行回收操作，避免浪费");
            }
            testHandlers();
        }finally {
            lockAddHandler.unlock();
        }
    }


    private void testHandlers(){
        System.out.println(JSON.toJSONString(handlers));
    }

    private String key(RmtAddress rmtAddress){
        return rmtAddress.getHost()+":"+rmtAddress.getPort()+"/"+rmtAddress.getServiceName();
    }
    //节点发生改变 重新管理handler
    public void serverNodeChange(CopyOnWriteArrayList<RmtAddress> currentAliveAllServerList){
        try {
            lockAddHandler.lock();
            //
            if(currentAliveAllServerList!=null && currentAliveAllServerList.size()<=0){//没有可用节点
                handlers.clear();
                logger.error("当前已无可用服务器节点，全挂了");
                return;
            }

            Map<String,RmtAddress> aliveMap = new HashMap<String,RmtAddress>(currentAliveAllServerList.size());
            for(RmtAddress rmtAddress:currentAliveAllServerList){
                aliveMap.put(key(rmtAddress),rmtAddress);
            }

            //筛查挂掉的服务器节点
            for(int i=0;i<handlers.size();i++){
                NettyClientHandler handler = handlers.get(i);
                RmtAddress old = handler.getRmtAddress();
                String key = key(old);
                if(!aliveMap.containsKey(key)){
                    old.setDead(true);
                    logger.error(key+"节点已经不存在,60秒后准备回收");
                    gcDelayHandler.delayGc(old,60,TimeUnit.SECONDS);
                }
            }
            //新增节点
            Map<String,NettyClientHandler> handlerMap = new HashMap<String,NettyClientHandler>(handlers.size());
            for(NettyClientHandler handler:handlers){
                handlerMap.put(key(handler.getRmtAddress()),handler);
            }
            for(RmtAddress alive:currentAliveAllServerList){
                String key = key(alive);
                if(!handlerMap.containsKey(key)){
                    //跟handlers比对 新增的节点就重新连接 缺失的节点就删除（延迟删除 避免硬断造成的问题）  不变的节点不变
                    ClientConnectPool.connect(alive);
                    logger.error(key+"新增节点，进行连接");
                }
            }
            //更新节点
            for(int i=0;i<handlers.size();i++){
                NettyClientHandler handler = handlers.get(i);
                RmtAddress old = handler.getRmtAddress();
                String key = key(old);
                if(aliveMap.containsKey(key)){
                    if(old.isDead()){
                        RmtAddress alive = aliveMap.get(key);
                        ClientConnectPool.connect(alive);
                        logger.info(key+"更新节点，重新进行连接");
                    }
                }
            }
            testHandlers();
        }finally {
            lockAddHandler.unlock();
        }
    }

    //垃圾回收
    private class GCDelayHandler{

        private ReentrantLock lock = new ReentrantLock();

        public void delayGc(RmtAddress gcRmtAddress,int times,TimeUnit unit){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    boolean successClear = true;
                    for(int z=0;z<10;z++){//最多尝试清理10次
                        try {
                            lock.lock();
                            try {
                                unit.sleep(times);
                                String gckey = key(gcRmtAddress);
                                for(int i=0;i<handlers.size();i++){
                                    NettyClientHandler handler = handlers.get(i);
                                    if(!handler.isWorking()){
                                        RmtAddress rmtAddress = handler.getRmtAddress();
                                        if(rmtAddress.isDead()){
                                            if(key(rmtAddress).equals(gckey)){
                                                handler.close();
                                                handlers.remove(i);
                                                logger.info(gckey+"节点已经被成功回收");
                                            }
                                        }
                                    }else {
                                        successClear = false;
                                        logger.error(gckey+"无法回收，当前处于忙碌状态");
                                    }
                                }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }finally {
                            lock.unlock();
                        }
                        if(successClear){
                            break;
                        }
                    }
                }
            });
            thread.setName("GCDelayHandler Thread");
            thread.start();

        }
    }

    //有了这个方法 就拿到netty客户端连接 就可以请求服务端了 所以 所有工作都围绕拿放handlers
    public NettyClientHandler chooseOneHandler(String serviceName){
        int size = size(serviceName);
        //可以考虑CAS算法
        if(size<=0){
            for (;;){
                //当前没有可用
                size = await(serviceName,3,TimeUnit.SECONDS);
                if(size>0){
                    break;
                }
            }
        }

        int index = (rerote.getAndIncrement()+size) % size;
        //归零 避免无限增大
        rerote.compareAndSet(size,0);
        return get(serviceName,index);
    }

    private NettyClientHandler get(String serviceName,int idx){
        List<NettyClientHandler> newList = new ArrayList<NettyClientHandler>();
        for(int i=0;i<handlers.size();i++){
            NettyClientHandler handler = handlers.get(i);
            RmtAddress rmtAddress = handler.getRmtAddress();
            if(!rmtAddress.isDead() && rmtAddress.getServiceName().equals(serviceName)){
                newList.add(handler);
            }
        }
        return newList.get(idx);
    }

    private int size(String serviceName){
        int size = 0;
        for(int i=0;i<handlers.size();i++){
            NettyClientHandler handler = handlers.get(i);
            RmtAddress rmtAddress = handler.getRmtAddress();
            if(!rmtAddress.isDead() && rmtAddress.getServiceName().equals(serviceName)){
                size++;
            }
        }
        return size;
    }

    private int await(String serviceName,long time, TimeUnit unit){
        int size = 0;
        try {
            lock.lock();
                logger.debug("寻找可用handler");
//            condition.await(time,unit); 配合lock使用会导致无法锁其他线程了 必须被唤醒
                size = size(serviceName);
                if(size<=0){
                    serverNodeChange(ClientFoundService.get().getServiceList());//重新刷新可用节点 锁避免多个线程竞争 带来非常多的调用
                    size = size(serviceName);
                    logger.debug("耗时寻找");
                }
                if(size<=0){
                    unit.sleep(time);
                }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
        return size;
    }

}
